/*
演示如何使用 WebClient 软件包发送 HTTP POST 请求并接收响应数据。
文件中的代码展示了如何创建 WebClient 会话、发送 POST 请求、
接收响应数据并处理内存分配错误等操作。
*/
#include <string.h>
#include <rtthread.h>
#include <webclient.h>
#include <stdio.h>
// 定义POST请求响应缓冲区的大小为1024字节
#define POST_RESP_BUFSZ                1024
// 定义POST请求头部缓冲区的大小为1024字节
#define POST_HEADER_BUFSZ              1024

char* xinpian = "772180287555";             // 芯片编码=通信编码=电子秤唯一编码
char* canpin = "0102250310001";             // 产品编码=出厂编码

typedef enum {
    API_GET_BALANCE_NO,      // 远程编码
    API_LOAD_KEY,            // 获取密钥
    API_BALANCE_INFO,        // 基础信息
    API_PAY_STATUS,          // 订单查询
    API_GET_PAY,             // 在线支付
    API_ORDER_ADD,           // 添加订单
    API_SET_FILE,            // 图片上报
    API_SET_STATUS,          // 心跳模块
    API_GET_BREED,           // 获得按钮
    API_SET_BREED_PRICE,     // 修改价格
    API_GET_VIP,             // 会员信息
    API_SET_BALANCE_LOG,     // 日志上报
    API_SET_POINTS,          // 订单补录
    API_GET_TIME,            // 服务器时间
    API_GET_VERSION          // 软件版本
} API_TYPE;

// API地址映射表
static const char* API_URI_MAP[] = {
    [API_GET_BALANCE_NO]    = "http://ast.mark.satrace.cn/api/v2/balance/get_balance_no",
    [API_LOAD_KEY]          = "http://ast.mark.satrace.cn/api/v2/balance/load_key",
    [API_BALANCE_INFO]      = "http://ast.mark.satrace.cn/api/v2/balance/balance_info",
    [API_PAY_STATUS]        = "http://ast.mark.satrace.cn/api/v2/balance/pay_status",
    [API_GET_PAY]           = "http://ast.mark.satrace.cn/api/v2/balance/get_pay",
    [API_ORDER_ADD]         = "http://ast.mark.satrace.cn/api/v2/balance/order_add",
    [API_SET_FILE]          = "http://ast.mark.satrace.cn/api/v2/balance/set_file",
    [API_SET_STATUS]        = "http://ast.mark.satrace.cn/api/v2/balance/set_status",
    [API_GET_BREED]         = "http://ast.mark.satrace.cn/api/v2/balance/get_breed",
    [API_SET_BREED_PRICE]   = "http://ast.mark.satrace.cn/api/v2/balance/set_breed_price",
    [API_GET_VIP]           = "http://ast.mark.satrace.cn/api/v2/balance/get_vip",
    [API_SET_BALANCE_LOG]   = "http://ast.mark.satrace.cn/api/v2/balance/set_balance_log",
    [API_SET_POINTS]        = "http://ast.mark.satrace.cn/api/v2/balance/set_points",
    [API_GET_TIME]          = "http://ast.mark.satrace.cn/api/v2/balance/get_time",
    [API_GET_VERSION]       = "http://ast.mark.satrace.cn/api/v2/balance/get_version"
};

// 获取API地址的宏
#define GET_API_URI(api_type) (API_URI_MAP[(api_type)])

/* 静态函数获取URI */
static inline const char* get_api_uri(API_TYPE type) {
    return API_URI_MAP[type];
}

// /* 函数调用初始化 */
// const char* POST_LOCAL_URI = NULL;
// void post_api_uris(char str) {
//     POST_LOCAL_URI = get_api_uri(str);
// }

// 获取时间戳函数（get）
// 定义一个静态函数get_timestamp，用于获取时间戳并存储在buffer中
static int get_timestamp(char *buffer, size_t buffer_size)
{
    // 定义一个指向webclient_session结构体的指针session，并初始化为RT_NULL
    struct webclient_session *session = RT_NULL;
    // 定义一个指向字符数组的指针response_buf，用于存储HTTP响应，并初始化为RT_NULL
    char *response_buf = RT_NULL;
    // 定义一个整型变量ret，用于存储函数返回值，并初始化为RT_EOK（表示成功）
    int ret = RT_EOK;
    // 定义一个常量字符串url，指向获取时间戳的URL
    const char *url = "http://acs.m.taobao.com/gw/mtop.common.getTimestamp/";

    // 创建一个webclient会话，缓冲区大小为512字节
    session = webclient_session_create(512);
    // 如果会话创建失败，打印错误信息并返回内存不足错误码
    if (!session) {
        rt_kprintf("[ERROR] Create session failed\n");
        return -RT_ENOMEM;
    }

    // 使用webclient发送GET请求，如果返回值不是200（HTTP OK），打印错误信息并设置返回值为错误码
    if ((ret = webclient_get(session, url)) != 200) {
        rt_kprintf("[ERROR] GET failed (%d)\n", ret);
        ret = -RT_ERROR;
        // 跳转到退出标签，进行资源释放
        goto __exit;
    }

    // 分配256字节的内存用于存储HTTP响应
    response_buf = web_malloc(256);
    // 如果内存分配失败，设置返回值为内存不足错误码并跳转到退出标签
    if (!response_buf) {
        ret = -RT_ENOMEM;
        goto __exit;
    }

    // 从webclient会话中读取响应数据到response_buf，最多读取256字节
    int total_read = webclient_read(session, response_buf, 256);
    // 如果读取失败或没有读取到数据，设置返回值为错误码并跳转到退出标签
    if (total_read <= 0) {
        ret = -RT_ERROR;
        goto __exit;
    }
    // 在response_buf的末尾添加字符串结束符
    response_buf[total_read] = '\0';

    // 在response_buf中查找时间戳的开始位置
    char *t_start = strstr(response_buf, "\"t\":\"");
    // 如果找到了时间戳的开始位置
    if (t_start) {
        // 移动指针到时间戳的实际开始位置
        t_start += 5;
        // 查找时间戳的结束位置
        char *t_end = strchr(t_start, '\"');
        // 如果找到了时间戳的结束位置
        if (t_end) {
            // 计算时间戳的长度
            int t_len = t_end - t_start;
            // 如果时间戳长度大于等于buffer_size，打印错误信息并设置返回值为错误码
            if (t_len >= buffer_size) {
                rt_kprintf("[ERROR] Timestamp too long\n");
                ret = -RT_ERROR;
                goto __exit;
            }
            // 将时间戳复制到buffer中
            strncpy(buffer, t_start, t_len);
            // 在buffer的末尾添加字符串结束符
            buffer[t_len] = '\0';
            // 打印获取到的时间戳
            rt_kprintf("[INFO] Time value: %s\n", buffer);
        } else {
            // 如果没有找到时间戳的结束位置，设置返回值为错误码
            ret = -RT_ERROR;
        }
    } else {
        // 如果没有找到时间戳的开始位置，打印错误信息并设置返回值为错误码
        rt_kprintf("[ERROR] Invalid response format\n");
        ret = -RT_ERROR;
    }

// 退出标签，用于释放资源
__exit:
    // 如果会话指针不为空，关闭会话
    if (session) webclient_close(session);
    // 如果响应缓冲区指针不为空，释放内存
    if (response_buf) web_free(response_buf);
    // 返回函数执行结果
    return ret;
}


// 定义一个指向常量字符数组的指针post_data
// const char *post_data = "{\"uniqueCode\":\"0102250310001\"}";  // 转义后的合法字符串

/* send HTTP POST request by common request interface, it used to receive longer data */
// 定义一个静态函数webclient_post_comm，用于发送POST请求并处理响应
/*核心功能
发送 HTTP POST 请求
将 post_data 数据通过 POST 方式发送到指定的 uri（服务器地址）。
自动添加必要的 HTTP 头部（如 Content-Length 和 Content-Type）。
支持处理二进制或文本数据（通过 application/octet-stream 类型）。
接收服务器响应
读取服务器返回的响应数据，并通过串口打印输出。
支持分块读取响应内容（循环读取直到数据结束）。
错误处理
内存分配失败时返回错误码（-RT_ENOMEM）。
检测 HTTP 响应状态码，若非 200 则视为错误。
确保资源释放（会话关闭、内存回收），避免内存泄漏*/
static int webclient_post_comm(const char *uri, const void *post_data, size_t data_len)
{
    // 定义一个指向webclient会话结构的指针session，并初始化为RT_NULL
    struct webclient_session* session = RT_NULL;
    // 定义一个指向unsigned char类型的指针buffer，并初始化为RT_NULL
    unsigned char *buffer = RT_NULL;
    // 定义一个整型变量index，用于循环计数
    int index, ret = 0;
    // 定义一个整型变量bytes_read，用于存储每次读取的字节数
    int bytes_read, resp_status;

    // 1. 分配响应缓冲区
    buffer = (unsigned char *) web_malloc(POST_RESP_BUFSZ);
    // 如果内存分配失败，打印错误信息并返回内存不足错误码
    if (buffer == RT_NULL)
    {
        rt_kprintf("no memory for receive response buffer.\n");
        ret = -RT_ENOMEM;
        goto __exit;// 内存不足时退出
    }

    // 2. 创建 WebClient 会话   
    /* create webclient session and set header response size */
    session = webclient_session_create(POST_HEADER_BUFSZ);// 初始化 HTTP 会话
    if (session == RT_NULL)
    {
        ret = -RT_ENOMEM;
        goto __exit; // 创建失败时退出
    }

    // 3. 添加 HTTP 头部
    /* build header for upload 用于上传的 build 标头*/
    webclient_header_fields_add(session, "Content-Length: %d\r\n", strlen(post_data));// 数据长度
    webclient_header_fields_add(session, "Content-Type: application/json\r\n");// 数据类型

    /* send POST request by default header */
    // 4. 发送 POST 请求，返200表示成功
    if ((resp_status = webclient_post(session, uri, post_data, data_len)) != 200) // 发送数据 
    {
        rt_kprintf("webclient POST request failed, response(%d) error.\n", resp_status);//打印POST 请求失败
        ret = -RT_ERROR;
        goto __exit;
    }

    // 5. 读取并打印服务器响应数据
    rt_kprintf("webclient post response data: \n");
    do
    {
        bytes_read = webclient_read(session, buffer, POST_RESP_BUFSZ);// 分块读取
        if (bytes_read <= 0)
        {
            break;
        }

        for (index = 0; index < bytes_read; index++)
        {
            rt_kprintf("%c", buffer[index]);// 逐字符打印响应内容
        }
    } while (1);

    rt_kprintf("\n");

__exit:
    if (session)
    {
        webclient_close(session);
        session = RT_NULL;
    }

    if (buffer)
    {
        web_free(buffer);
    }

    return ret;
}

/* send HTTP POST request by simplify request interface, it used to received shorter data */
// 定义一个静态函数webclient_post_smpl，用于发送HTTP POST请求并处理响应
// 参数uri：请求的URI
// 参数post_data：POST请求的数据
// 参数data_len：POST请求数据的长度
static int webclient_post_smpl(const char *uri, const char *post_data, size_t data_len)
{
    // 定义一个指针response，用于存储服务器响应的数据，初始为RT_NULL
    char *response = RT_NULL;
    // 定义一个指针header，用于存储HTTP请求头，初始为RT_NULL
    char *header = RT_NULL;
    // 定义一个变量resp_len，用于存储服务器响应的数据长度，初始为0
    size_t resp_len = 0;
    // 定义一个变量index，用于循环遍历响应数据，初始为0
    int index = 0;

    // 添加Content-Length头到HTTP请求头中，值是post_data的长度
    webclient_request_header_add(&header, "Content-Length: %d\r\n", strlen(post_data));
    // 添加Content-Type头到HTTP请求头中，值是application/json
    webclient_request_header_add(&header, "Content-Type: application/json\r\n");

    // 发送HTTP POST请求，并接收服务器响应
    // 如果请求失败，打印错误信息，释放header内存，返回-RT_ERROR
    if (webclient_request(uri, header, post_data, data_len, (void **)&response, &resp_len) < 0)
    {
        rt_kprintf("webclient send post request failed.");
        web_free(header);
        return -RT_ERROR;
    }

    // 打印发送POST请求成功的提示信息
    rt_kprintf("webclient send post request by simplify request interface.\n");
    // 打印服务器响应的数据提示信息
    rt_kprintf("webclient post response data: \n");
    // 遍历响应数据并逐字符打印
    for (index = 0; index < resp_len; index++)
    {
        rt_kprintf("%c", response[index]);
    }
    // 打印换行符
    rt_kprintf("\n");

    // 如果header不为空，释放header内存
    if (header)
    {
        web_free(header);
    }

    // 如果response不为空，释放response内存
    if (response)
    {
        web_free(response);
    }

    // 返回0表示函数执行成功
    return 0;
}

//命令使用的函数
int webclient_post_test_API(int argc, char **argv, char* post_data)
{
    char *uri = RT_NULL; // 初始化URI指针为NULL
    // init_api_uris(API_GET_BALANCE_NO);
    if (argc < 2)    // 如果参数个数为1
    {
        uri = web_strdup(argv[1]); // 复制默认的POST本地URI
        if(uri == RT_NULL) // 如果内存分配失败
        {
            rt_kprintf("no memory for create post request uri buffer.\n"); // 打印错误信息
            return -RT_ENOMEM; // 返回内存不足错误码
        }
        //const char *post_data = "RT-Thread is an open source IoT operating system from China!";
        webclient_post_comm(uri, (void *)post_data, strlen(post_data)); // 发送普通POST请求
    }
    else if (argc == 2) // 如果参数个数为2
    {
        if (strcmp(argv[1], "-s") == 0) // 如果第一个参数是"-s"
        {
            uri = web_strdup(argv[1]); // 复制默认的POST本地URI
            if(uri == RT_NULL) // 如果内存分配失败
            {
                rt_kprintf("no memory for create post request uri buffer.\n"); // 打印错误信息
                return -RT_ENOMEM; // 返回内存不足错误码
            }

            webclient_post_smpl(uri, (void *)post_data, strlen(post_data)); // 发送简化POST请求
        }
        else // 如果第一个参数不是"-s"
        {
            uri = web_strdup(argv[1]); // 复制用户提供的URI
            if(uri == RT_NULL) // 如果内存分配失败
            {
                rt_kprintf("no memory for create post request uri buffer.\n"); // 打印错误信息
                return -RT_ENOMEM; // 返回内存不足错误码
            }
            webclient_post_comm(uri, (void *)post_data, strlen(post_data)); // 发送普通POST请求
        }
    }
    else if(argc == 3 && strcmp(argv[1], "-s") == 0) // 如果参数个数为3且第一个参数是"-s"
    {
        uri = web_strdup(argv[2]); // 复制用户提供的URI
        if(uri == RT_NULL) // 如果内存分配失败
        {
            rt_kprintf("no memory for create post request uri buffer.\n"); // 打印错误信息
            return -RT_ENOMEM; // 返回内存不足错误码
        }

        webclient_post_smpl(uri, (void *)post_data, strlen(post_data)); // 发送简化POST请求
    }
    else // 如果参数个数不符合要求
    {
        rt_kprintf("web_post_test [uri]     - webclient post request test.\n"); // 打印使用说明
        rt_kprintf("web_post_test -s [uri]  - webclient simplify post request test.\n"); // 打印使用说明
        return -RT_ERROR; // 返回错误码
    }

    if (uri) // 如果URI指针不为NULL
    {
        web_free(uri); // 释放URI内存
    }

    return RT_EOK; // 返回成功码
}


/* 增强版API请求函数 */
int post_api_uris(API_TYPE api_type)
{
    int ret = RT_EOK;
    char* post_json = RT_NULL;      // 需要发送的json数据
    const char *uri = RT_NULL;      //对接的api地址
    char time_val[32] = {0};        // 定义一个时间戳数组，用于存储时间戳字符串
    
    // 校验码 使用 HMAC SHA256 (Base64) 签名方法进行签名,非空参数
    int* sha256_sign = 0; 
    // 电子秤加载全部配置信息,0不加载，1全部加载
    int* dzc_isload = 1;
    
    /* 根据API类型生成不同JSON数据 */
    switch (api_type)
    {
        // 获取电子秤密钥
        case API_LOAD_KEY:
        {
            post_json = web_malloc(256);    // 分配256字节的内存空间给post_json变量，用于存储JSON数据
            // 时间戳获取，转化为秒
            ret = (get_timestamp(time_val, sizeof(time_val))/1000);
            if (ret != RT_EOK) {
                rt_kprintf("[ERROR] Failed to get timestamp\n");
                return ret;
            }
            snprintf(post_json, 256,
                "{\"key\":\"%s\",\"isload\":\"%d\",\"timestamp\":\"%s\",\"sign\":\"%d\"}",
                xinpian, dzc_isload, time_val, sha256_sign);
            break;
        }
        // 获取台秤基础信息
        case API_BALANCE_INFO:
        {
            post_json = web_malloc(256);  
            // 时间戳获取，转化为秒
            ret = (get_timestamp(time_val, sizeof(time_val))/1000);
            if (ret != RT_EOK) {
                rt_kprintf("[ERROR] Failed to get timestamp\n");
                return ret;
            } 
            snprintf(post_json, 256,
                "{\"key\":\"%s\",\"isload\":\"%d\",\"timestamp\":\"%s\",\"sign\":\"%d\"}",
                xinpian, dzc_isload, time_val, sha256_sign);
            break;
        }
        // 订单状态查询
        case API_PAY_STATUS:
        {
            post_json = web_malloc(256);  
            // 时间戳获取，转化为秒
            ret = (get_timestamp(time_val, sizeof(time_val))/1000);
            if (ret != RT_EOK) {
                rt_kprintf("[ERROR] Failed to get timestamp\n");
                return ret;
            } 
            snprintf(post_json, 256,
                "{\"key\":\"%s\",\"isload\":\"%d\",\"timestamp\":\"%s\",\"sign\":\"%d\"}",
                xinpian, dzc_isload, time_val, sha256_sign);
            break;
        }

        default:
            rt_kprintf("暂未实现的API类型: %d\n", api_type);
            return -RT_EINVAL;
    }

    memset(time_val, 0, sizeof(time_val));      // 清空时间戳

    /* 检查内存分配 */
    if (!post_json) {
        rt_kprintf("Memory allocation failed\n");
        return -RT_ENOMEM;
    }

    /* 获取API地址 */
    uri = get_api_uri(api_type);
    if (!uri) {
        rt_kprintf("Invalid API URI\n");
        web_free(post_json);
        return -RT_ERROR;
    }

    /* 发送请求 */
    char* argv[] = { "web_post_test", (char*)uri };
    ret = webclient_post_test_API(2, argv, post_json);

    web_free(post_json);
    return ret;
}

// 旧命令入口函数
// #ifdef FINSH_USING_MSH
// #include <finsh.h>//   命令入口函数         开启命令
// MSH_CMD_EXPORT_ALIAS(webclient_post_test_API, web_post_test, webclient post request test.);
// #endif /* FINSH_USING_MSH */

#ifdef FINSH_USING_MSH
#include <finsh.h>
/* API类型映射表 */
static const struct {
    const char *name;
    API_TYPE    type;
} api_type_map[] = {
    {"get_balance",   API_GET_BALANCE_NO},      // 通过远程编码获得在线通信编码
    {"load_key",      API_LOAD_KEY},             // 获取电子秤密钥
    {"balance_info",  API_BALANCE_INFO},         // 获取台秤基础信息
    {"pay_status",    API_PAY_STATUS},           // 订单状态查询
    {"get_pay",       API_GET_PAY},             // 在线订单支付v2
    {"order_add",     API_ORDER_ADD},           // 添加订单
    {"set_file",      API_SET_FILE},            //台秤订单图片上报
    {"set_status",    API_SET_STATUS},          //电子秤心跳模块
    {"get_breed",     API_GET_BREED},           //获得台秤按钮
    {"set_price",     API_SET_BREED_PRICE},      //修改按钮价格
    {"get_vip",       API_GET_VIP},             //获取会员卡解密信息
    {"log_report",    API_SET_BALANCE_LOG},     //电子秤设备日志上报
    {"add_points",    API_SET_POINTS},          //订单补录买家信息
    {"get_time",      API_GET_TIME},            //获取服务器时间
    {"get_version",   API_GET_VERSION}          //获取软件版本接口
};

/* 新命令入口函数 */
static void api_cmd(int argc, char **argv) 
{
    if (argc != 2) {
        rt_kprintf("Usage: api_call <type>\n"
                   "Available types:\n"
                   "get_balance, load_key, balance_info, pay_status, get_pay, order_add, set_file, set_status, get_breed, set_price, get_vip, log_report, add_points, get_time, get_version\n");
        return;
    }

    /* 查找匹配的API类型 */
    for (size_t i = 0; i < sizeof(api_type_map)/sizeof(api_type_map[0]); i++) {
        if (strcmp(argv[1], api_type_map[i].name) == 0) {
            int ret = post_api_uris(api_type_map[i].type);
            rt_kprintf("API调用结果: %s\n", ret == RT_EOK ? "成功" : "失败");
            return;
        }
    }
    
    rt_kprintf("无效的API类型!\n");
}

/* 注册新命令 */
MSH_CMD_EXPORT_ALIAS(api_cmd, api_call, Call specified API interface);
#endif /* FINSH_USING_MSH */

/*
编译并运行程序： 确保你的 RT-Thread 环境已经正确配置，并且该文件已经编译进你的固件中。启动你的设备并进入 RT-Thread 的命令行界面。

使用命令调用 API： 你可以使用 api_call 命令来调用不同的 API 接口。命令格式如下：
api_call <type>
示例：

获取远程编码：
api_call get_balance
获取电子秤密钥：
api_call load_key
获取台秤基础信息：
api_call balance_info
查询订单状态：
api_call pay_status     

*/